Skip to content

turbo-tasks: task-storage memory wins#93720

Open
lukesandberg wants to merge 4 commits into
canaryfrom
05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec
Open

turbo-tasks: task-storage memory wins#93720
lukesandberg wants to merge 4 commits into
canaryfrom
05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec

Conversation

@lukesandberg
Copy link
Copy Markdown
Contributor

@lukesandberg lukesandberg commented May 9, 2026

Summary

Four small, independent changes that shrink TaskStorage and the data it owns:

Recommend reviewing commit-by-commit

  1. Arc<CachedTaskType>triomphe::Arc<CachedTaskType>. triomphe::Arc is already a workspace dep used in ReadRef / SharedReference. CachedTaskType never appears in a Weak<...>, so we can drop the weak count and the CAS in drop_slow. Saves one usize per allocation. Migrated via a CachedTaskTypeArc newtype so the bincode Encode/Decode impls don't need to cross the orphan rule.

  2. Niche-encode CellDependency. The cell_dependencies / cell_dependents sets used to hold (CellRef, Option<u64>) tuples — Option<u64> cost a full 16 B (8 B discriminant + 8 B value, aligned), making each element 32 B. A CellDependency enum with two variants (All(CellRef) / Hash(CellRef, u64)) lets the layout algorithm reuse the niche on ValueTypeId (NonZero<u16>) inside CellRef.cell.type_id for the variant tag. Element size drops 32 → 24 B; LazyField from 56 → 48 B. The same enum backs both forward and reverse edges — for cell_dependents we re-point CellRef.task at the dependent task.

    Added CellDependency::into_parts() and use it in iter_cell_dependents / iter_cell_dependencies hot loops so the discriminant is checked once instead of twice via back-to-back cell_ref() + key() calls.

  3. TaskStorage::lazy: Vec<LazyField>TinyVec<LazyField>. The lazy vec only ever holds ~25 elements (one per declared lazy field in the schema). Swapping Vec's 24 B (ptr, len, cap) header for (ptr, len: u8, cap: u8) + 6 B padding gives 16 B. Drops size_of::<TaskStorage>() from 136 → 128 B.

    TinyVec is hand-rolled so I added a push/iter micro-benchmark to confirm it doesn't lose performance vs std Vec. Results below.

  4. Rightsize collections → Explore the AutoSet/AutoMap types in storage_schema and ensure each one is maximally sized for its natural alignment.

Benchmark results

next build on a representative app (15 runs each, M4 Pro, caffeinate -dimsu nice -n -20)

Fresh same-day baseline against branch:

metric canary branch Δ 95% CI significant?
wall time 40.83s 41.12s +0.7% [−1.07s, +1.64s] no
user time 282.27s 283.21s +0.3% [−1.02s, +2.89s] no
sys time 69.38s 71.26s +2.7% [−1.54s, +5.32s] no
MaxRSS 12.47 GB 12.04 GB −3.4% [−0.48 GB, −0.38 GB] yes

MaxRSS is the headline. −0.43 GB on a 12.5 GB working set, with t=−17.86 (every branch run lower than every canary run, CV ≤ 0.6% on both sides). Wall / user / sys are all within noise — this PR is a memory win with no measurable timing impact.

TinyVec vs Vec micro-bench (turbo-tasks/benches/tiny_vec.rs, 200 samples each)

n Vec push TinyVec push Δ% Vec iter TinyVec iter Δ%
0 1.31ns 894ps −31.8% 598ps 596ps −0.4%
1 16.92ns 14.75ns −12.9% 964ps 952ps −1.2%
4 17.93ns 15.93ns −11.1% 1.49ns 1.50ns +0.5%
8 63.13ns 45.24ns −28.3% 1.97ns 1.96ns −0.2%
16 97.35ns 79.91ns −17.9% 3.16ns 3.14ns −0.5%
24 137.41ns 119.88ns −12.8% 4.30ns 4.30ns +0.0%

TinyVec push is 11–32% faster than Vec push across all realistic sizes; iter is identical. Run with cargo bench -p turbo-tasks --bench tiny_vec.

task_overhead/turbo Criterion bench (M4 Pro, --sample-size 200)

variant dur canary branch Δ significant?
turbo-uncached 1µs 9.77 µs 9.68 µs −1.0% yes
turbo-uncached 1000µs 1.01 ms 1.01 ms −0.1% yes
turbo-cached-same-keys 1µs 198.6 ns 191.9 ns −3.4% yes
turbo-cached-same-keys 100µs 226.5 ns 208.1 ns −8.1% yes
turbo-cached-different-keys 1µs 233.8 ns 224.1 ns −4.2% yes
turbo-cached-different-keys 100µs 305.3 ns 246.9 ns −19.1% yes
turbo-uncached-parallel 10µs 1.63 µs 1.54 µs −5.8% yes
turbo-uncached-parallel 100µs 8.41 µs 7.88 µs −6.3% yes

Copy link
Copy Markdown
Contributor Author

lukesandberg commented May 9, 2026

@github-actions github-actions Bot added created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js. labels May 9, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 9, 2026

Comment thread turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs Outdated
Comment thread turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs Outdated
Comment thread turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs Outdated
@lukesandberg lukesandberg changed the title turbo-tasks: migrate CachedTaskType reference-counting to triomphe::Arc turbo-tasks: task-storage memory and dispatch-path overhead reductions May 9, 2026
Comment thread turbopack/crates/turbo-tasks-backend/src/data.rs Outdated
Comment thread turbopack/crates/turbo-tasks-backend/src/data.rs Outdated
Comment thread turbopack/crates/turbo-tasks-macros/src/derive/task_storage_macro.rs Outdated
Comment thread turbopack/crates/turbo-tasks/src/backend.rs
Comment thread turbopack/crates/turbo-tasks/src/backend.rs
Comment thread turbopack/crates/turbo-tasks/src/backend.rs Outdated
Comment thread turbopack/crates/turbo-tasks/src/lazy_vec.rs Outdated
Comment thread turbopack/crates/turbo-tasks/src/raw_vc.rs Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 10, 2026

Stats from current PR

✅ No significant changes detected

📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 811ms 811ms █▅▄▅▂
Cold (Ready in log) 787ms 787ms ▅▁▄▃▄
Cold (First Request) 1.224s 1.221s ▆▁▃▃▃
Warm (Listen) 810ms 811ms ▆▆▁▃▁
Warm (Ready in log) 782ms 787ms ▄▁▅▃▄
Warm (First Request) 599ms 607ms ▄▁▅▃▅
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 811ms 811ms ▆▃▆▆▆
Cold (Ready in log) 787ms 787ms ▃▃▅▃▃
Cold (First Request) 3.227s 3.201s ▂▁▃▁▂
Warm (Listen) 811ms 810ms █▁█▁█
Warm (Ready in log) 787ms 786ms ▃▃▆▂▃
Warm (First Request) 3.232s 3.263s ▃▁▃▂▄

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 4.775s 4.737s ▃▆▃▂▂
Cached Build 4.774s 4.790s ▄▂▃▃▃
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 23.516s 23.753s █▃▃▁▂
Cached Build 23.895s 23.874s ▂▂▄▃▄
node_modules Size 506 MB 506 MB ▁████
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles
Canary PR Change
04hm05ar7kldw.js gzip 5.73 kB N/A -
055aw4u7hn1jv.js gzip 70.9 kB N/A -
0cz1d0mv5g_q7.js gzip 39.4 kB 39.4 kB
0dvitrl5zg37g.js gzip 8.82 kB N/A -
0p7tkfsxmv8ep.js gzip 156 B N/A -
0qiq-ikey17ei.js gzip 155 B N/A -
0sf7ysou-72zd.js gzip 8.71 kB N/A -
0vcchqb24-h55.js gzip 154 B N/A -
157abun3hwc_s.js gzip 10.3 kB N/A -
1elt1qium-r2m.css gzip 115 B 115 B
1jj68jv9537mc.js gzip 13.8 kB N/A -
1jpaub6y8xlfr.js gzip 2.3 kB N/A -
1ot0mvscrc_uf.js gzip 233 B N/A -
1v_7mahx-79v3.js gzip 156 B N/A -
1zabrqj73lcq7.js gzip 156 B N/A -
2_m3xv2uq3sjc.js gzip 1.46 kB N/A -
20_bzd4cnuk6f.js gzip 65.6 kB N/A -
20jgkju22ogb8.js gzip 50.4 kB N/A -
23abq4bpnyu6s.js gzip 161 B N/A -
24-18647la7oc.js gzip 157 B N/A -
24y34mwgrkqp4.js gzip 8.78 kB N/A -
26twnknhm5qim.js gzip 153 B N/A -
2c-fd4y1zozz8.js gzip 8.79 kB N/A -
2d7416h_xd36x.js gzip 8.71 kB N/A -
2efjr9qeqzf_t.js gzip 155 B N/A -
2g21ny1t2kw37.js gzip 7.61 kB N/A -
2lyuhit6rn8fy.js gzip 9.44 kB N/A -
2q0gr8wfr3jwl.js gzip 8.77 kB N/A -
2t9e75oz6r0zp.js gzip 8.76 kB N/A -
2uku_olcn15b7.js gzip 8.79 kB N/A -
30r8mm-46bdqy.js gzip 220 B 220 B
39sl-keubxrt6.js gzip 155 B N/A -
3c1jdxkzlb8oq.js gzip 12.9 kB N/A -
3inab2jybr4k9.js gzip 450 B N/A -
3jkm5tdjvaf_q.js gzip 13.1 kB N/A -
3mt67agm5wp40.js gzip 10.6 kB N/A -
3qpe66ftr5vit.js gzip 159 B N/A -
3saabek4kohwi.js gzip 10 kB N/A -
4189xmby9yu1p.js gzip 13.6 kB N/A -
42n48pbsa6px8.js gzip 170 B N/A -
44e-mbst2zrbx.js gzip 152 B N/A -
turbopack-04..knzn.js gzip 4.2 kB N/A -
turbopack-06..dcr2.js gzip 4.2 kB N/A -
turbopack-0b..mvsr.js gzip 4.2 kB N/A -
turbopack-0v..h8au.js gzip 4.2 kB N/A -
turbopack-16..hufr.js gzip 4.21 kB N/A -
turbopack-19..dqzv.js gzip 4.2 kB N/A -
turbopack-1p..obkm.js gzip 4.2 kB N/A -
turbopack-1q..z-fe.js gzip 4.2 kB N/A -
turbopack-2-..u6zx.js gzip 4.2 kB N/A -
turbopack-25..aolk.js gzip 4.18 kB N/A -
turbopack-29..gj28.js gzip 4.2 kB N/A -
turbopack-2j..nryu.js gzip 4.2 kB N/A -
turbopack-3v..30l9.js gzip 4.2 kB N/A -
turbopack-3y..zp_c.js gzip 4.2 kB N/A -
0_i7nqgx23st7.js gzip N/A 10 kB -
06puhytyxk31p.js gzip N/A 8.82 kB -
0ay0mlrxf1iru.js gzip N/A 169 B -
0c1g7k_q0eabe.js gzip N/A 157 B -
0ca7-y17yur_x.js gzip N/A 156 B -
0f0wgs50fuvzz.js gzip N/A 156 B -
0j42f9zonj0wd.js gzip N/A 13 kB -
0m34gln_kt4fg.js gzip N/A 5.73 kB -
0rm_nj33c0r4u.js gzip N/A 154 B -
0xrdnbkg2vvjy.js gzip N/A 161 B -
0yhzk2i5kx_og.js gzip N/A 70.9 kB -
1ck72gbqbfell.js gzip N/A 50.4 kB -
1g3q1ww01thnl.js gzip N/A 2.3 kB -
1hraqxuiymq6v.js gzip N/A 8.79 kB -
1l9un1sl77287.js gzip N/A 1.46 kB -
1r3hpm0mji0fy.js gzip N/A 161 B -
20xc0m_sca76i.js gzip N/A 156 B -
21-eavqb1k_36.js gzip N/A 13.9 kB -
2147zgtf14z-q.js gzip N/A 234 B -
23bz3xsg-5-1s.js gzip N/A 8.71 kB -
23d1a7-y0i08o.js gzip N/A 158 B -
241h-8q5lq_re.js gzip N/A 153 B -
25-_uslqos021.js gzip N/A 155 B -
27441mytv7pbm.js gzip N/A 9.43 kB -
2cjkwjgm1zcfs.js gzip N/A 8.71 kB -
2scd8zaoyb8md.js gzip N/A 8.79 kB -
2st_qs6p_9us0.js gzip N/A 13.1 kB -
2tgrnri27_15r.js gzip N/A 65.6 kB -
2zo2exm1d8qj1.js gzip N/A 13.6 kB -
30yt3aos91w3x.js gzip N/A 154 B -
3b36esolmu3k4.js gzip N/A 154 B -
3f710q6kll2xn.js gzip N/A 7.61 kB -
3hn75zuxly9az.js gzip N/A 10.3 kB -
3hqh7m128tvsn.js gzip N/A 8.77 kB -
3hqti_t-zy1x4.js gzip N/A 449 B -
3mnawenie1flm.js gzip N/A 8.76 kB -
3ubsozlu6zs38.js gzip N/A 10.6 kB -
43iwfqjnx1cy_.js gzip N/A 8.78 kB -
turbopack-01..8uo5.js gzip N/A 4.2 kB -
turbopack-07..uids.js gzip N/A 4.2 kB -
turbopack-0e..fe4b.js gzip N/A 4.2 kB -
turbopack-0n..r8yb.js gzip N/A 4.2 kB -
turbopack-0p..zz-y.js gzip N/A 4.2 kB -
turbopack-0v..fhv1.js gzip N/A 4.21 kB -
turbopack-11..0dwi.js gzip N/A 4.18 kB -
turbopack-1j..2nkx.js gzip N/A 4.19 kB -
turbopack-2_..bnhj.js gzip N/A 4.2 kB -
turbopack-2i..-kdt.js gzip N/A 4.19 kB -
turbopack-31..amhb.js gzip N/A 4.2 kB -
turbopack-3o..8eau.js gzip N/A 4.2 kB -
turbopack-3w..x2yg.js gzip N/A 4.2 kB -
turbopack-43..g_vg.js gzip N/A 4.2 kB -
Total 469 kB 469 kB ⚠️ +41 B

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 721 B 720 B
Total 721 B 720 B ✅ -1 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 434 B 429 B 🟢 5 B (-1%)
Total 434 B 429 B ✅ -5 B

📦 Webpack

Client

Main Bundles
Canary PR Change
2258-HASH.js gzip 61.4 kB N/A -
2266-HASH.js gzip 4.69 kB N/A -
3317.HASH.js gzip 169 B N/A -
4866-HASH.js gzip 5.64 kB N/A -
9e302639-HASH.js gzip 62.8 kB N/A -
framework-HASH.js gzip 59.5 kB 59.5 kB
main-app-HASH.js gzip 255 B 254 B
main-HASH.js gzip 39.9 kB 39.9 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
175fd0fd-HASH.js gzip N/A 62.8 kB -
2596-HASH.js gzip N/A 5.63 kB -
34-HASH.js gzip N/A 61.3 kB -
5691.HASH.js gzip N/A 169 B -
9156-HASH.js gzip N/A 4.68 kB -
Total 236 kB 236 kB ✅ -99 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 193 B 193 B
_error-HASH.js gzip 181 B 182 B
css-HASH.js gzip 334 B 332 B
dynamic-HASH.js gzip 1.79 kB 1.81 kB
edge-ssr-HASH.js gzip 255 B 255 B
head-HASH.js gzip 351 B 348 B
hooks-HASH.js gzip 385 B 384 B
image-HASH.js gzip 580 B 580 B
index-HASH.js gzip 257 B 259 B
link-HASH.js gzip 2.51 kB 2.52 kB
routerDirect..HASH.js gzip 318 B 319 B
script-HASH.js gzip 387 B 386 B
withRouter-HASH.js gzip 316 B 316 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.97 kB 7.99 kB ⚠️ +19 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 126 kB 126 kB
page.js gzip 276 kB 270 kB 🟢 5.3 kB (-2%)
Total 402 kB 396 kB ✅ -5.4 kB
Middleware
Canary PR Change
middleware-b..fest.js gzip 621 B 614 B 🟢 7 B (-1%)
middleware-r..fest.js gzip 155 B 155 B
middleware.js gzip 44.5 kB 44.9 kB
edge-runtime..pack.js gzip 842 B 842 B
Total 46.1 kB 46.5 kB ⚠️ +356 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 719 B 717 B
Total 719 B 717 B ✅ -2 B
Build Cache
Canary PR Change
0.pack gzip 4.49 MB 4.49 MB
index.pack gzip 117 kB 113 kB 🟢 3.7 kB (-3%)
index.pack.old gzip 115 kB 114 kB
Total 4.72 MB 4.71 MB ✅ -6.93 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 351 kB 351 kB
app-page-exp..prod.js gzip 195 kB 195 kB
app-page-tur...dev.js gzip 350 kB 350 kB
app-page-tur..prod.js gzip 194 kB 194 kB
app-page-tur...dev.js gzip 347 kB 347 kB
app-page-tur..prod.js gzip 192 kB 192 kB
app-page.run...dev.js gzip 347 kB 347 kB
app-page.run..prod.js gzip 193 kB 193 kB
app-route-ex...dev.js gzip 77.5 kB 77.5 kB
app-route-ex..prod.js gzip 52.9 kB 52.9 kB
app-route-tu...dev.js gzip 77.6 kB 77.6 kB
app-route-tu..prod.js gzip 52.9 kB 52.9 kB
app-route-tu...dev.js gzip 77.2 kB 77.2 kB
app-route-tu..prod.js gzip 52.7 kB 52.7 kB
app-route.ru...dev.js gzip 77.1 kB 77.1 kB
app-route.ru..prod.js gzip 52.7 kB 52.7 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 44.3 kB 44.3 kB
pages-api-tu..prod.js gzip 33.8 kB 33.8 kB
pages-api.ru...dev.js gzip 44.3 kB 44.3 kB
pages-api.ru..prod.js gzip 33.7 kB 33.7 kB
pages-turbo....dev.js gzip 53.7 kB 53.7 kB
pages-turbo...prod.js gzip 39.4 kB 39.4 kB
pages.runtim...dev.js gzip 53.6 kB 53.6 kB
pages.runtim..prod.js gzip 39.4 kB 39.4 kB
server.runti..prod.js gzip 63.1 kB 63.1 kB
use-cache-pr...dev.js gzip 69.7 kB 69.7 kB
use-cache-pr...dev.js gzip 69.7 kB 69.7 kB
use-cache-pr...dev.js gzip 68 kB 68 kB
use-cache-pr...dev.js gzip 68 kB 68 kB
Total 3.37 MB 3.37 MB
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/0938c4ce43a5244add0c7ee9a027b5e2a8c2d897/next

Commit: 0938c4c

@lukesandberg lukesandberg force-pushed the 05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec branch from dbced22 to 50785a8 Compare May 12, 2026 16:40
@lukesandberg lukesandberg changed the title turbo-tasks: task-storage memory and dispatch-path overhead reductions turbo-tasks: task-storage memory wins May 12, 2026
@lukesandberg lukesandberg force-pushed the 05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec branch from 2f21410 to cf840de Compare May 12, 2026 21:26
@lukesandberg lukesandberg marked this pull request as ready for review May 12, 2026 21:32
@lukesandberg lukesandberg requested a review from sokra May 12, 2026 21:32
@lukesandberg lukesandberg force-pushed the 05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec branch 2 times, most recently from 42ece5a to 7221af0 Compare May 19, 2026 02:12
Migrate Arc<CachedTaskType> to triomphe::Arc via CachedTaskTypeArc newtype.
Saves one usize per allocation (no weak count) and avoids the weak-count
CAS in drop_slow compared to std::sync::Arc. We never need
Weak<CachedTaskType>, so the trade-off is favorable.
Replace the `(CellRef, Option<u64>)` and `(CellId, Option<u64>, TaskId)`
tuples used for cell-edge tracking with a `CellDependency` enum:

    enum CellDependency {
        All(CellRef),
        Hash(CellRef, u64),
    }

`Option<u64>` previously cost a full 16 B (8 B discriminant + 8 B value,
aligned). With an explicit enum the layout algorithm reuses the niche on
`ValueTypeId` (`NonZero<u16>`) inside `CellRef.cell.type_id` for the
variant tag, dropping the element from 32 B to 24 B. That in turn shrinks
`LazyField` from 56 B to 48 B.

Also adds `CellDependency::into_parts()` and uses it in
`iter_cell_dependents` / `iter_cell_dependencies` hot loops to avoid
checking the enum discriminant twice via back-to-back
`cell_ref()` + `key()` calls.
Replace `TaskStorage::lazy: Vec<LazyField>` with a custom 16 B `TinyVec`
(u8 len + u8 cap, the schema's max field count is well under 255).
Drops `size_of::<TaskStorage>()` from 136 B to 128 B.

Included micro-benchmarks show `TinyVec` push is 11-32% faster than `Vec`
across realistic sizes and iter is neutral.

The `TinyVec` type:

* Carries a `const MAX: u8` generic parameter that strictly caps push count
  and tightens the growth schedule (doubles until it would exceed MAX, then
  caps exactly at MAX). The schema macro emits `TinyVec<LazyField, N>` where
  `N` is the exact lazy-field count, so the cap matches the actual schema
  size (e.g. with 24 variants we end at cap=24 instead of cap=32, saving a
  few slots per fully-populated task). A compile-time static assert rejects
  `MAX = 0` at monomorphization.
* Tightens visibility: `new`, `capacity`, `as_slice`, `as_mut_slice`,
  `reserve` are private; `len` / `is_empty` stay pub as a clippy-preferred
  pair.
* Delegates `retain_mut` to `Vec::retain_mut` via a `Vec::from_raw_parts`
  round-trip — `retain_mut` is cold relative to push so the round-trip cost
  is irrelevant, and this drops ~7 unsafe blocks with a panic partial-shift
  guard.
* Delegates owned `IntoIter` to `std::vec::IntoIter` via the same Vec
  round-trip, dropping ~50 lines of unsafe.
* Drops `Extend` and `FromIterator` trait impls; the only caller path uses
  an inherent `extend_exact` which requires an `ExactSizeIterator` and
  reserves exactly once.
* Drops `iter`, `iter_mut`, `last_mut`, `Index`, `IndexMut` — all reachable
  through `Deref<Target = [T]>`. `for x in &tv` / `for x in &mut tv` still
  need `IntoIterator` impls for refs because the `for`-loop desugar doesn't
  apply `Deref` coercion across the reference boundary.

Net unsafe count in `TinyVec` is 5 in the hot path plus 1 in `retain_mut`
and 1 in `IntoIter` — each upholding a single local invariant or just
round-tripping through Vec.
Each schema field's `I` (inline capacity) was previously fixed at 1. With
`SmallVec`'s `union` feature on, the heap variant occupies 16 bytes
(`NonNull<T> + usize`), so the SmallVec body is always `max(16, N * sizeof(T))`
aligned to `max(align(T), 8)`. Net: growing `N` is free until `N * sizeof(T)`
exceeds 16, after which each step adds `align_up(sizeof(T), 8)` bytes.

Two opportunities follow:

* For lazy fields, the `LazyField` enum already pays a 40-byte payload
  budget (the largest variants — `cell_data`, `cell_data_hash`,
  `AutoSet<CellDependency>`, `CounterMap<CollectibleRef, i32>` — saturate
  it at I=1). Smaller-element fields sat at 32 B with 8 B of unused
  padding. Bumping them to fill 40 B is zero-cost: TaskStorage and
  LazyField sizes don't change.

* For inline fields on TaskStorage, raising `I` is free up to the SmallVec
  16-byte body limit (e.g. `AutoSet<TaskId>` to I=4, `CounterMap<TaskId,
  u32>` to I=2).

To allow per-field tuning, parameterize:

* `CounterMap` over `const I: usize`, propagating to its `AutoMap` inner.
* The `AutoSet` / `AutoMap` schema aliases drop their hardcoded `1`; each
  field declares its own `I`.

Per-field choices:

* `output_dependent` (inline) `AutoSet<TaskId>` → 4 (32 B)
* `upper` (inline) `CounterMap<TaskId, u32>` → 2 (32 B)
* `children`, `output_dependencies`, `outdated_output_dependencies`
  `AutoSet<TaskId>` → 6 (40 B)
* `collectibles_dependencies`, `outdated_collectibles_dependencies`
  `AutoSet<CollectiblesRef>` → 3 (40 B)
* `collectibles_dependents` `AutoSet<(TraitTypeId, TaskId)>` → 3 (40 B)
* `followers`, `aggregated_dirty_containers`,
  `aggregated_current_session_clean_containers`
  `CounterMap<TaskId, _>` → 3 (40 B)
* `cell_type_max_index` `AutoMap<ValueTypeId, u32>` → 3 (40 B)

Variants already at 40 B (cell_data, cell_data_hash, AutoSet<CellDependency>,
CounterMap<CollectibleRef, i32>) stay at I=1. `in_progress_cells` stays at
I=1 to avoid overflowing LazyField under the `hanging_detection` feature
(which inflates `Event` from 8 B to 16 B).

`TaskStorage` stays at 128 B, `LazyField` at 48 B; only inline capacities
change.
@lukesandberg lukesandberg force-pushed the 05-09-turbo-tasks_replace_taskstorage_lazy_vec_with_a_16_b_lazyvec branch from 7221af0 to 0938c4c Compare May 19, 2026 06:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Turbopack team PRs by the Turbopack team. Turbopack Related to Turbopack with Next.js.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant